/**
 * PANDA 3D SOFTWARE
 * Copyright (c) Carnegie Mellon University.  All rights reserved.
 *
 * All use of this software is subject to the terms of the revised BSD
 * license.  You should have received a copy of this license along
 * with this source code in a file named "LICENSE."
 *
 * @file pgItem.cxx
 * @author drose
 * @date 2002-03-13
 */

#include "pgItem.h"
#include "pgMouseWatcherParameter.h"
#include "pgCullTraverser.h"
#include "config_pgui.h"
#include "boundingVolume.h"
#include "pandaNode.h"
#include "sceneGraphReducer.h"
#include "throw_event.h"
#include "string_utils.h"
#include "nodePath.h"
#include "cullTraverser.h"
#include "cullTraverserData.h"
#include "cullBinManager.h"
#include "clipPlaneAttrib.h"
#include "scissorAttrib.h"
#include "dcast.h"
#include "boundingSphere.h"
#include "boundingBox.h"
#include "config_mathutil.h"
#include "pipeline.h"

#ifdef HAVE_AUDIO
#include "audioSound.h"
#endif

using std::string;

TypeHandle PGItem::_type_handle;
PT(TextNode) PGItem::_text_node;
PGItem *PGItem::_focus_item = nullptr;
PGItem::BackgroundFocus PGItem::_background_focus;


/**
 * Returns true if the 2-d v1 is to the right of v2.
 */
INLINE bool
is_right(const LVector2 &v1, const LVector2 &v2) {
  return (v1[0] * v2[1] - v1[1] * v2[0]) > 0;
}

/**
 *
 */
PGItem::
PGItem(const string &name) :
  PandaNode(name),
  _lock(name),
  _notify(nullptr),
  _has_frame(false),
  _frame(0, 0, 0, 0),
  _state(0),
  _flags(0),
  _region(new PGMouseWatcherRegion(this))
{
  set_cull_callback();
  set_renderable();
}

/**
 *
 */
PGItem::
~PGItem() {
  if (_notify != nullptr) {
    _notify->remove_item(this);
    _notify = nullptr;
  }

  nassertv(_region->_item == this);
  _region->_item = nullptr;

  set_background_focus(false);
  if (_focus_item == this) {
    _focus_item = nullptr;
  }
}

/**
 *
 */
PGItem::
PGItem(const PGItem &copy) :
  PandaNode(copy),
  _notify(nullptr),
  _has_frame(copy._has_frame),
  _frame(copy._frame),
  _state(copy._state),
  _flags(copy._flags),
  _region(new PGMouseWatcherRegion(this))
#ifdef HAVE_AUDIO
  , _sounds(copy._sounds)
#endif
{
  // We give our region the same name as the region for the PGItem we're
  // copying--so that this PGItem will generate the same event names when the
  // user interacts with it.
  _region->set_name(copy._region->get_name());

  // Make a deep copy of all of the original PGItem's StateDefs.
  size_t num_state_defs = copy._state_defs.size();
  _state_defs.reserve(num_state_defs);
  for (size_t i = 0; i < num_state_defs; ++i) {
    // We cheat and cast away the const, because the frame is just a cache.
    // But we have to get the frame out of the source before we can safely
    // copy it.
    StateDef &old_sd = (StateDef &)(copy._state_defs[i]);
    old_sd._frame.remove_node();
    old_sd._frame_stale = true;

    StateDef new_sd;
    new_sd._root = old_sd._root.copy_to(NodePath());
    new_sd._frame_style = old_sd._frame_style;

    _state_defs.push_back(new_sd);

#ifdef THREADED_PIPELINE
    if (Pipeline::get_render_pipeline()->get_num_stages() > 1) {
      ((PGItem &)copy).update_frame((int)i);
      update_frame((int)i);
    }
#endif
  }
}

/**
 * Returns a newly-allocated Node that is a shallow copy of this one.  It will
 * be a different Node pointer, but its internal data may or may not be shared
 * with that of the original Node.
 */
PandaNode *PGItem::
make_copy() const {
  LightReMutexHolder holder(_lock);
  return new PGItem(*this);
}

/**
 * Called after the node's transform has been changed for any reason, this
 * just provides a hook so derived classes can do something special in this
 * case.
 */
void PGItem::
transform_changed() {
  LightReMutexHolder holder(_lock);
  PandaNode::transform_changed();
  if (_notify != nullptr) {
    _notify->item_transform_changed(this);
  }
}

/**
 * Called after the node's draw_mask has been changed for any reason, this
 * just provides a hook so derived classes can do something special in this
 * case.
 */
void PGItem::
draw_mask_changed() {
  LightReMutexHolder holder(_lock);
  PandaNode::draw_mask_changed();
  if (_notify != nullptr) {
    _notify->item_draw_mask_changed(this);
  }
}

/**
 * This function will be called during the cull traversal to perform any
 * additional operations that should be performed at cull time.  This may
 * include additional manipulation of render state or additional
 * visible/invisible decisions, or any other arbitrary operation.
 *
 * Note that this function will *not* be called unless set_cull_callback() is
 * called in the constructor of the derived class.  It is necessary to call
 * set_cull_callback() to indicated that we require cull_callback() to be
 * called.
 *
 * By the time this function is called, the node has already passed the
 * bounding-volume test for the viewing frustum, and the node's transform and
 * state have already been applied to the indicated CullTraverserData object.
 *
 * The return value is true if this node should be visible, or false if it
 * should be culled.
 */
bool PGItem::
cull_callback(CullTraverser *trav, CullTraverserData &data) {
  CullTraverser::_pgui_nodes_pcollector.add_level(1);

  // We try not to hold the lock for longer than necessary.
  PT(PandaNode) state_def_root;
  bool has_frame;
  PGMouseWatcherRegion *region;
  {
    LightReMutexHolder holder(_lock);
    has_frame = _has_frame && ((_flags & F_active) != 0);
    region = _region;

    int state = _state;
    if (state >= 0 && (size_t)state < _state_defs.size()) {
      StateDef &state_def = _state_defs[state];
      if (!state_def._root.is_empty()) {
        if (Thread::get_current_pipeline_stage() == 0) {
          if (state_def._frame_stale) {
            update_frame(state);
          }
        }

        state_def_root = state_def._root.node();
      }
    }
  }

  if (has_frame && !data.is_this_node_hidden(trav->get_camera_mask())) {
    // The item has a frame, so we want to generate a region for it and update
    // the MouseWatcher.

    // We can only do this if our traverser is a PGCullTraverser (which will
    // be the case if this node was parented somewhere under a PGTop node).
    if (trav->is_exact_type(PGCullTraverser::get_class_type())) {
      PGCullTraverser *pg_trav;
      DCAST_INTO_R(pg_trav, trav, true);

      const LMatrix4 &transform = data.get_net_transform(trav)->get_mat();

      // Consider the cull bin this object is in.  Since the binning affects
      // the render order, we want bins that render later to get higher sort
      // values.
      int bin_index = data._state->get_bin_index();
      int sort;

      CullBinManager *bin_manager = CullBinManager::get_global_ptr();
      CullBinManager::BinType bin_type = bin_manager->get_bin_type(bin_index);
      if (bin_type == CullBinManager::BT_fixed) {
        // If the bin is a "fixed" type bin, our local sort is based on the
        // fixed order.
        sort = data._state->get_draw_order();

      } else if (bin_type == CullBinManager::BT_unsorted) {
        // If the bin is an "unsorted" type bin, we base the local sort on the
        // scene graph order.
        sort = pg_trav->_sort_index;
        pg_trav->_sort_index++;

      } else {
        // Otherwise, the local sort is irrelevant.
        sort = 0;
      }

      // Now what order does this bin sort relative to the other bins?  This
      // becomes the high-order part of the final sort count.
      int bin_sort = bin_manager->get_bin_sort(data._state->get_bin_index());

      // Combine the two sorts into a single int.  This assumes we only need
      // 16 bits for each sort number, possibly an erroneous assumption.  We
      // should really provide two separate sort values, both ints, in the
      // MouseWatcherRegion; but in the interest of expediency we work within
      // the existing interface which only provides one.
      sort = (bin_sort << 16) | ((sort + 0x8000) & 0xffff);

      const ClipPlaneAttrib *clip = nullptr;
      const ScissorAttrib *scissor = nullptr;
      data._state->get_attrib(clip);
      data._state->get_attrib(scissor);
      if (activate_region(transform, sort, clip, scissor)) {
        pg_trav->_top->add_region(region);
      }
    }
  }

  if (state_def_root != nullptr) {
    // This item has a current state definition that we should use to render
    // the item.
    trav->traverse_down(data, state_def_root);
  }

  // Now continue to render everything else below this node.
  return true;
}

/**
 * Called when needed to recompute the node's _internal_bound object.  Nodes
 * that contain anything of substance should redefine this to do the right
 * thing.
 */
void PGItem::
compute_internal_bounds(CPT(BoundingVolume) &internal_bounds,
                        int &internal_vertices,
                        int pipeline_stage,
                        Thread *current_thread) const {
  LightReMutexHolder holder(_lock, current_thread);
  int num_vertices = 0;

  // First, get ourselves a fresh, empty bounding volume.
  PT(BoundingVolume) bound;

  BoundingVolume::BoundsType btype = get_bounds_type();
  if (btype == BoundingVolume::BT_default) {
    btype = bounds_type;
  }

  if (btype == BoundingVolume::BT_sphere) {
    bound = new BoundingSphere;
  } else {
    bound = new BoundingBox;
  }

  // Now actually compute the bounding volume by putting it around all of our
  // states' bounding volumes.
  pvector<const BoundingVolume *> child_volumes;

  // We walk through the list of state defs indirectly, calling
  // get_state_def() on each one, to ensure that the frames are updated
  // correctly before we measure their bounding volumes.
  for (int i = 0; i < (int)_state_defs.size(); i++) {
    NodePath &root = ((PGItem *)this)->do_get_state_def(i);
    if (!root.is_empty()) {
      PandaNode *node = root.node();
      child_volumes.push_back(node->get_bounds(current_thread));
      num_vertices += node->get_nested_vertices(current_thread);
    }
  }

  const BoundingVolume **child_begin = &child_volumes[0];
  const BoundingVolume **child_end = child_begin + child_volumes.size();

  bound->around(child_begin, child_end);

  internal_bounds = bound;
  internal_vertices = num_vertices;
}

/**
 * The recursive implementation of prepare_scene(). Don't call this directly;
 * call PandaNode::prepare_scene() or NodePath::prepare_scene() instead.
 */
void PGItem::
r_prepare_scene(GraphicsStateGuardianBase *gsg, const RenderState *node_state,
                GeomTransformer &transformer, Thread *current_thread) {
  LightReMutexHolder holder(_lock);
  for (StateDef &def : _state_defs) {
    NodePath &root = def._root;
    if (!root.is_empty()) {
      PandaNode *child = root.node();
      CPT(RenderState) child_state = node_state->compose(child->get_state());
      child->r_prepare_scene(gsg, child_state, transformer, current_thread);
    }
  }

  PandaNode::r_prepare_scene(gsg, node_state, transformer, current_thread);
}

/**
 * Transforms the contents of this node by the indicated matrix, if it means
 * anything to do so.  For most kinds of nodes, this does nothing.
 */
void PGItem::
xform(const LMatrix4 &mat) {
  LightReMutexHolder holder(_lock);
  // Transform the frame.
  LPoint3 ll(_frame[0], 0.0f, _frame[2]);
  LPoint3 ur(_frame[1], 0.0f, _frame[3]);
  ll = ll * mat;
  ur = ur * mat;
  _frame.set(ll[0], ur[0], ll[2], ur[2]);

  // Transform the individual states and their frame styles.
  for (size_t state = 0; state < _state_defs.size(); ++state) {
    StateDef &def = _state_defs[state];
    NodePath &root = def._root;
    // Apply the matrix to the previous transform.
    root.set_transform(root.get_transform()->compose(TransformState::make_mat(mat)));

    // Now flatten the transform into the subgraph.
    SceneGraphReducer gr;
    gr.apply_attribs(root.node());

    // Transform the frame style too.
    if (def._frame_style.xform(mat)) {
#ifdef THREADED_PIPELINE
      if (Pipeline::get_render_pipeline()->get_num_stages() > 1) {
        update_frame((int)state);
      }
      else
#endif
      {
        def._frame_stale = true;
      }
    }
  }
  mark_internal_bounds_stale();
}

/**
 * Applies the indicated scene graph transform and order as determined by the
 * traversal from PGTop.
 *
 * The return value is true if the region is valid, or false if it is empty or
 * completely clipped.
 */
bool PGItem::
activate_region(const LMatrix4 &transform, int sort,
                const ClipPlaneAttrib *cpa,
                const ScissorAttrib *sa) {
  using std::min;
  using std::max;

  LightReMutexHolder holder(_lock);
  // Transform all four vertices, and get the new bounding box.  This way the
  // region works (mostly) even if has been rotated.
  LPoint3 ll = LPoint3::rfu(_frame[0], 0.0f, _frame[2]) * transform;
  LPoint3 lr = LPoint3::rfu(_frame[1], 0.0f, _frame[2]) * transform;
  LPoint3 ul = LPoint3::rfu(_frame[0], 0.0f, _frame[3]) * transform;
  LPoint3 ur = LPoint3::rfu(_frame[1], 0.0f, _frame[3]) * transform;
  LVector3 up = LVector3::up();
  int up_axis;
  if (up[1]) {
    up_axis = 1;
  }
  else if (up[2]) {
    up_axis = 2;
  }
  else {
    up_axis = 0;
  }
  LVector3 right = LVector3::right();
  int right_axis;
  if (right[0]) {
    right_axis = 0;
  }
  else if (right[2]) {
    right_axis = 2;
  }
  else {
    right_axis = 1;
  }

  LVecBase4 frame;
  if (cpa != nullptr && cpa->get_num_on_planes() != 0) {
    // Apply the clip plane(s) andor scissor region now that we are here in
    // world space.

    ClipPoints points;
    points.reserve(4);
    points.push_back(LPoint2(ll[right_axis], ll[up_axis]));
    points.push_back(LPoint2(lr[right_axis], lr[up_axis]));
    points.push_back(LPoint2(ur[right_axis], ur[up_axis]));
    points.push_back(LPoint2(ul[right_axis], ul[up_axis]));

    int num_on_planes = cpa->get_num_on_planes();
    for (int i = 0; i < num_on_planes; ++i) {
      NodePath plane_path = cpa->get_on_plane(i);
      LPlane plane = DCAST(PlaneNode, plane_path.node())->get_plane();
      plane.xform(plane_path.get_net_transform()->get_mat());

      // We ignore the forward axis, assuming the frame is still in the right-
      // up plane after being transformed.  Not sure if we really need to
      // support general 3-D transforms on 2-D objects.
      clip_frame(points, plane);
    }

    if (points.empty()) {
      // Turns out it's completely clipped after all.
      return false;
    }

    ClipPoints::iterator pi;
    pi = points.begin();
    frame.set((*pi)[0], (*pi)[0], (*pi)[1], (*pi)[1]);
    ++pi;
    while (pi != points.end()) {
      frame[0] = min(frame[0], (*pi)[0]);
      frame[1] = max(frame[1], (*pi)[0]);
      frame[2] = min(frame[2], (*pi)[1]);
      frame[3] = max(frame[3], (*pi)[1]);
      ++pi;
    }
  } else {
    // Since there are no clip planes involved, just set the frame.
    frame.set(min(min(ll[right_axis], lr[right_axis]), min(ul[right_axis], ur[right_axis])),
              max(max(ll[right_axis], lr[right_axis]), max(ul[right_axis], ur[right_axis])),
              min(min(ll[up_axis], lr[up_axis]), min(ul[up_axis], ur[up_axis])),
              max(max(ll[up_axis], lr[up_axis]), max(ul[up_axis], ur[up_axis])));
  }

  if (sa != nullptr) {
    // Also restrict it to within the scissor region.
    const LVecBase4 &sf = sa->get_frame();
    // Expand sf from 0..1 to -1..1.
    frame.set(max(frame[0], sf[0] * 2.0f - 1.0f),
              min(frame[1], sf[1] * 2.0f - 1.0f),
              max(frame[2], sf[2] * 2.0f - 1.0f),
              min(frame[3], sf[3] * 2.0f - 1.0f));
    if (frame[1] <= frame[0] || frame[3] <= frame[2]) {
      // Completely outside the scissor region.
      return false;
    }
  }

  _region->set_frame(frame);

  _region->set_sort(sort);
  _region->set_active(true);

  // calculate the inverse of this transform, which is needed to go back to
  // the frame space.
  _frame_inv_xform.invert_from(transform);

  return true;
}

/**
 * This is a callback hook function, called whenever the mouse enters the
 * region.  The mouse is only considered to be "entered" in one region at a
 * time; in the case of nested regions, it exits the outer region before
 * entering the inner one.
 */
void PGItem::
enter_region(const MouseWatcherParameter &param) {
  LightReMutexHolder holder(_lock);
  if (pgui_cat.is_debug()) {
    pgui_cat.debug()
      << *this << "::enter_region(" << param << ")\n";
  }

  PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
  string event = get_enter_event();
  play_sound(event);
  throw_event(event, EventParameter(ep));

  if (_notify != nullptr) {
    _notify->item_enter(this, param);
  }
}

/**
 * This is a callback hook function, called whenever the mouse exits the
 * region.  The mouse is only considered to be "entered" in one region at a
 * time; in the case of nested regions, it exits the outer region before
 * entering the inner one.
 */
void PGItem::
exit_region(const MouseWatcherParameter &param) {
  LightReMutexHolder holder(_lock);
  if (pgui_cat.is_debug()) {
    pgui_cat.debug()
      << *this << "::exit_region(" << param << ")\n";
  }

  PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
  string event = get_exit_event();
  play_sound(event);
  throw_event(event, EventParameter(ep));

  if (_notify != nullptr) {
    _notify->item_exit(this, param);
  }

  // pgui_cat.info() << get_name() << "::exit()" << endl;
}

/**
 * This is a callback hook function, called whenever the mouse moves within
 * the boundaries of the region, even if it is also within the boundaries of a
 * nested region.  This is different from "enter", which is only called
 * whenever the mouse is within only that region.
 */
void PGItem::
within_region(const MouseWatcherParameter &param) {
  LightReMutexHolder holder(_lock);
  if (pgui_cat.is_debug()) {
    pgui_cat.debug()
      << *this << "::within_region(" << param << ")\n";
  }

  PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
  string event = get_within_event();
  play_sound(event);
  throw_event(event, EventParameter(ep));

  if (_notify != nullptr) {
    _notify->item_within(this, param);
  }
}

/**
 * This is a callback hook function, called whenever the mouse moves
 * completely outside the boundaries of the region.  See within().
 */
void PGItem::
without_region(const MouseWatcherParameter &param) {
  LightReMutexHolder holder(_lock);
  if (pgui_cat.is_debug()) {
    pgui_cat.debug()
      << *this << "::without_region(" << param << ")\n";
  }

  PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
  string event = get_without_event();
  play_sound(event);
  throw_event(event, EventParameter(ep));

  if (_notify != nullptr) {
    _notify->item_without(this, param);
  }
}

/**
 * This is a callback hook function, called whenever the widget gets the
 * keyboard focus.
 */
void PGItem::
focus_in() {
  LightReMutexHolder holder(_lock);
  if (pgui_cat.is_debug()) {
    pgui_cat.debug()
      << *this << "::focus_in()\n";
  }

  string event = get_focus_in_event();
  play_sound(event);
  throw_event(event);

  if (_notify != nullptr) {
    _notify->item_focus_in(this);
  }
}

/**
 * This is a callback hook function, called whenever the widget loses the
 * keyboard focus.
 */
void PGItem::
focus_out() {
  LightReMutexHolder holder(_lock);
  if (pgui_cat.is_debug()) {
    pgui_cat.debug()
      << *this << "::focus_out()\n";
  }

  string event = get_focus_out_event();
  play_sound(event);
  throw_event(event);

  if (_notify != nullptr) {
    _notify->item_focus_out(this);
  }
}

/**
 * This is a callback hook function, called whenever a mouse or keyboard
 * button is depressed while the mouse is within the region.
 */
void PGItem::
press(const MouseWatcherParameter &param, bool background) {
  LightReMutexHolder holder(_lock);
  if (pgui_cat.is_debug()) {
    pgui_cat.debug()
      << *this << "::press(" << param << ", " << background << ")\n";
  }

  if (!background) {
    PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
    string event;
    if (param.is_keyrepeat()) {
      event = get_repeat_event(param.get_button());
    } else {
      event = get_press_event(param.get_button());
    }
    play_sound(event);
    throw_event(event, EventParameter(ep));
  }

  if (_notify != nullptr) {
    _notify->item_press(this, param);
  }
}

/**
 * This is a callback hook function, called whenever a mouse or keyboard
 * button previously depressed with press() is released.
 */
void PGItem::
release(const MouseWatcherParameter &param, bool background) {
  LightReMutexHolder holder(_lock);
  if (pgui_cat.is_debug()) {
    pgui_cat.debug()
      << *this << "::release(" << param << ", " << background << ")\n";
  }

  if (!background) {
    PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
    string event = get_release_event(param.get_button());
    play_sound(event);
    throw_event(event, EventParameter(ep));
  }

  if (_notify != nullptr) {
    _notify->item_release(this, param);
  }
}

/**
 * This is a callback hook function, called whenever the user presses a key.
 */
void PGItem::
keystroke(const MouseWatcherParameter &param, bool background) {
  LightReMutexHolder holder(_lock);
  if (pgui_cat.is_debug()) {
    pgui_cat.debug()
      << *this << "::keystroke(" << param << ", " << background << ")\n";
  }

  if (!background) {
    PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
    string event = get_keystroke_event();
    play_sound(event);
    throw_event(event, EventParameter(ep));

    if (has_notify()) {
      get_notify()->item_keystroke(this, param);
    }
  }
}

/**
 * This is a callback hook function, called whenever the user highlights an
 * option in the IME window.
 */
void PGItem::
candidate(const MouseWatcherParameter &param, bool background) {
  LightReMutexHolder holder(_lock);
  if (pgui_cat.is_debug()) {
    pgui_cat.debug()
      << *this << "::candidate(" << param << ", " << background << ")\n";
  }

  // We don't throw sound events for candidate selections for now.
  if (!background) {
    if (has_notify()) {
      get_notify()->item_candidate(this, param);
    }
  }
}

/**
 * This is a callback hook function, called whenever a mouse is moved while
 * within the region.
 */
void PGItem::
move(const MouseWatcherParameter &param) {
  LightReMutexHolder holder(_lock);
  if (pgui_cat.is_debug()) {
    pgui_cat.debug()
      << *this << "::move(" << param << ")\n";
  }

  if (_notify != nullptr) {
    _notify->item_move(this, param);
  }
}

/**
 * Calls press() on all the PGItems with background focus.
 */
void PGItem::
background_press(const MouseWatcherParameter &param) {
  // We have to be careful, because objects may remove themselves from the set
  // while we're iterating over it.
  auto it = _background_focus.begin();
  while (it != _background_focus.end()) {
    PGItem *item = *it++;
    if (!item->get_focus()) {
      PT(PGItem) item_ref(item);
      item_ref->press(param, true);
    }
  }
}

/**
 * Calls release() on all the PGItems with background focus.
 */
void PGItem::
background_release(const MouseWatcherParameter &param) {
  auto it = _background_focus.begin();
  while (it != _background_focus.end()) {
    PGItem *item = *it++;
    if (!item->get_focus()) {
      PT(PGItem) item_ref(item);
      item_ref->release(param, true);
    }
  }
}

/**
 * Calls keystroke() on all the PGItems with background focus.
 */
void PGItem::
background_keystroke(const MouseWatcherParameter &param) {
  auto it = _background_focus.begin();
  while (it != _background_focus.end()) {
    PGItem *item = *it++;
    if (!item->get_focus()) {
      PT(PGItem) item_ref(item);
      item_ref->keystroke(param, true);
    }
  }
}

/**
 * Calls candidate() on all the PGItems with background focus.
 */
void PGItem::
background_candidate(const MouseWatcherParameter &param) {
  auto it = _background_focus.begin();
  while (it != _background_focus.end()) {
    PGItem *item = *it++;
    if (!item->get_focus()) {
      PT(PGItem) item_ref(item);
      item_ref->candidate(param, true);
    }
  }
}

/**
 * Sets whether the PGItem is active for mouse watching.  This is not
 * necessarily related to the active/inactive appearance of the item, which is
 * controlled by set_state(), but it does affect whether it responds to mouse
 * events.
 */
void PGItem::
set_active(bool active) {
  LightReMutexHolder holder(_lock);
  if (active) {
    _flags |= F_active;
  } else {
    _flags &= ~F_active;
    // Deactivating the item automatically defocuses it too.
    if (get_focus()) {
      set_focus(false);
    }
  }
}

/**
 * Sets whether the PGItem currently has keyboard focus.  This simply means
 * that the item may respond to keyboard events as well as to mouse events;
 * precisely what this means is up to the individual item.
 *
 * Only one PGItem in the world is allowed to have focus at any given time.
 * Setting the focus on any other item automatically disables the focus from
 * the previous item.
 */
void PGItem::
set_focus(bool focus) {
  LightReMutexHolder holder(_lock);
  if (focus) {
    if (!get_active()) {
      // Cannot set focus on an inactive item.
      return;
    }

    // Set the keyboard focus to this item.
    if (_focus_item != this) {
      if (_focus_item != nullptr) {
        // Clear the focus from whatever item currently has it.
        _focus_item->set_focus(false);
      }
      _focus_item = this;
    }
    if (!get_focus()) {
      focus_in();
      _flags |= F_focus;
    }

  } else {
    if (_focus_item == this) {
      // Remove this item from the focus.
      _focus_item = nullptr;
    }

    if (get_focus()) {
      focus_out();
      _flags &= ~F_focus;
    }
  }
  _region->set_keyboard(focus);
}

/**
 * Sets the background_focus flag for this item.  When background_focus is
 * enabled, the item will receive keypress events even if it is not in focus;
 * in fact, even if it is not onscreen.  Unlike normal focus, many items may
 * have background_focus simultaneously.
 */
void PGItem::
set_background_focus(bool focus) {
  LightReMutexHolder holder(_lock);
  if (focus != get_background_focus()) {
    if (focus) {
      // Activate background focus.
      _flags |= F_background_focus;
      bool inserted = _background_focus.insert(this).second;
      nassertv(inserted);

    } else {
      // Deactivate background focus.
      _flags &= ~F_background_focus;
      size_t num_erased = _background_focus.erase(this);
      nassertv(num_erased == 1);
    }
  }
}

/**
 * Returns one more than the highest-numbered state def that was ever assigned
 * to the PGItem.  The complete set of state defs assigned may then be
 * retrieved by indexing from 0 to (get_num_state_defs() - 1).
 *
 * This is only an upper limit on the actual number of state defs, since there
 * may be holes in the list.
 */
int PGItem::
get_num_state_defs() const {
  LightReMutexHolder holder(_lock);
  return _state_defs.size();
}

/**
 * Returns true if get_state_def() has ever been called for the indicated
 * state (thus defining a render subgraph for this state index), false
 * otherwise.
 */
bool PGItem::
has_state_def(int state) const {
  LightReMutexHolder holder(_lock);
  if (state < 0 || state >= (int)_state_defs.size()) {
    return false;
  }
  return (!_state_defs[state]._root.is_empty());
}

/**
 * Resets the NodePath assigned to the indicated state to its initial default,
 * with only a frame representation if appropriate.
 */
void PGItem::
clear_state_def(int state) {
  LightReMutexHolder holder(_lock);
  if (state < 0 || state >= (int)_state_defs.size()) {
    return;
  }

  _state_defs[state]._root = NodePath();
  _state_defs[state]._frame = NodePath();
  _state_defs[state]._frame_stale = true;

  mark_internal_bounds_stale();

#ifdef THREADED_PIPELINE
  if (Pipeline::get_render_pipeline()->get_num_stages() > 1) {
    update_frame(state);
  }
#endif
}

/**
 * Parents an instance of the bottom node of the indicated NodePath to the
 * indicated state index.
 */
NodePath PGItem::
instance_to_state_def(int state, const NodePath &path) {
  LightReMutexHolder holder(_lock);
  if (path.is_empty()) {
    // If the source is empty, quietly do nothing.
    return NodePath();
  }

  mark_internal_bounds_stale();

  return path.instance_to(do_get_state_def(state));
}

/**
 * Returns the kind of frame that will be drawn behind the item when it is in
 * the indicated state.
 */
PGFrameStyle PGItem::
get_frame_style(int state) {
  LightReMutexHolder holder(_lock);
  if (state < 0 || state >= (int)_state_defs.size()) {
    return PGFrameStyle();
  }
  return _state_defs[state]._frame_style;
}

/**
 * Changes the kind of frame that will be drawn behind the item when it is in
 * the indicated state.
 */
void PGItem::
set_frame_style(int state, const PGFrameStyle &style) {
  LightReMutexHolder holder(_lock);

  slot_state_def(state);

  if (_state_defs[state]._root.is_empty()) {
    // Create a new node.
    _state_defs[state]._root = NodePath("state_" + format_string(state));
  }

  _state_defs[state]._frame_style = style;
  _state_defs[state]._frame_stale = true;

  mark_internal_bounds_stale();
  update_frame(state);
}

#ifdef HAVE_AUDIO
/**
 * Sets the sound that will be played whenever the indicated event occurs.
 */
void PGItem::
set_sound(const string &event, AudioSound *sound) {
  LightReMutexHolder holder(_lock);
  _sounds[event] = sound;
}

/**
 * Removes the sound associated with the indicated event.
 */
void PGItem::
clear_sound(const string &event) {
  LightReMutexHolder holder(_lock);
  _sounds.erase(event);
}

/**
 * Returns the sound associated with the indicated event, or NULL if there is
 * no associated sound.
 */
AudioSound *PGItem::
get_sound(const string &event) const {
  LightReMutexHolder holder(_lock);
  Sounds::const_iterator si = _sounds.find(event);
  if (si != _sounds.end()) {
    return (*si).second;
  }
  return nullptr;
}

/**
 * Returns true if there is a sound associated with the indicated event, or
 * false otherwise.
 */
bool PGItem::
has_sound(const string &event) const {
  LightReMutexHolder holder(_lock);
  return (_sounds.count(event) != 0);
}
#endif  // HAVE_AUDIO

/**
 * Returns the TextNode object that will be used by all PGItems to generate
 * default labels given a string.  This can be loaded with the default font,
 * etc.
 */
TextNode *PGItem::
get_text_node() {
  if (_text_node == nullptr) {
    _text_node = new TextNode("pguiText");
    _text_node->set_text_color(0.0f, 0.0f, 0.0f, 1.0f);

    // The default TextNode is aligned to the left, for the convenience of
    // PGEntry.
    _text_node->set_align(TextNode::A_left);
  }
  return _text_node;
}

/**
 * Plays the sound associated with the indicated event, if there is one.
 */
void PGItem::
play_sound(const string &event) {
#ifdef HAVE_AUDIO
  LightReMutexHolder holder(_lock);
  Sounds::const_iterator si = _sounds.find(event);
  if (si != _sounds.end()) {
    AudioSound *sound = (*si).second;
    sound->play();
  }
#endif  // HAVE_AUDIO
}

/**
 * The frame parameter is an in/out parameter.  This function adjusts frame so
 * that it represents the largest part of the rectangular region passed in,
 * that does not overlap with the rectangular region of the indicated
 * obscurer.  If the obscurer is NULL, or is a hidden node, it is not
 * considered and the frame is left unchanged.
 *
 * This is used by slider bars and scroll frames, which have to automatically
 * figure out how much space they have to work with after allowing space for
 * scroll bars and buttons.
 */
void PGItem::
reduce_region(LVecBase4 &frame, PGItem *obscurer) const {
  using std::min;
  using std::max;

  if (obscurer != nullptr && !obscurer->is_overall_hidden()) {
    LVecBase4 oframe = get_relative_frame(obscurer);

    // Determine the four rectangular regions on the four sides of the
    // obscuring region.
    LVecBase4 right(max(frame[0], oframe[1]), frame[1], frame[2], frame[3]);
    LVecBase4 left(frame[0], min(frame[1], oframe[0]), frame[2], frame[3]);
    LVecBase4 above(frame[0], frame[1], max(frame[2], oframe[3]), frame[3]);
    LVecBase4 below(frame[0], frame[1], frame[2], min(frame[3], oframe[2]));

    // Now choose the largest of those four.
    const LVecBase4 *largest = &right;
    PN_stdfloat largest_area = compute_area(*largest);
    compare_largest(largest, largest_area, &left);
    compare_largest(largest, largest_area, &above);
    compare_largest(largest, largest_area, &below);

    frame = *largest;
  }
}

/**
 * Returns the LVecBase4 frame of the indicated item, converted into this
 * item's coordinate space.  Presumably, item is a child of this node.
 */
LVecBase4 PGItem::
get_relative_frame(PGItem *item) const {
  using std::min;
  using std::max;

  NodePath this_np = NodePath::any_path((PGItem *)this);
  NodePath item_np = this_np.find_path_to(item);
  if (item_np.is_empty()) {
    item_np = NodePath::any_path(item);
  }
  const LVecBase4 &orig_frame = item->get_frame();
  LMatrix4 transform = item_np.get_mat(this_np);

  // Transform the item's frame into the PGScrollFrame's coordinate space.
  // Transform all four vertices, and get the new bounding box.  This way the
  // region works (mostly) even if has been rotated.
  LPoint3 ll(orig_frame[0], 0.0f, orig_frame[2]);
  LPoint3 lr(orig_frame[1], 0.0f, orig_frame[2]);
  LPoint3 ul(orig_frame[0], 0.0f, orig_frame[3]);
  LPoint3 ur(orig_frame[1], 0.0f, orig_frame[3]);
  ll = ll * transform;
  lr = lr * transform;
  ul = ul * transform;
  ur = ur * transform;

  return LVecBase4(min(min(ll[0], lr[0]), min(ul[0], ur[0])),
                    max(max(ll[0], lr[0]), max(ul[0], ur[0])),
                    min(min(ll[2], lr[2]), min(ul[2], ur[2])),
                    max(max(ll[2], lr[2]), max(ul[2], ur[2])));
}

/**
 * Converts from the 2-d mouse coordinates into the coordinate space of the
 * item.
 */
LPoint3 PGItem::
mouse_to_local(const LPoint2 &mouse_point) const {
  // This is ambiguous if the PGItem has multiple instances.  Why would you do
  // that, anyway?
  NodePath this_np((PGItem *)this);
  CPT(TransformState) inv_transform = NodePath().get_transform(this_np);
  return inv_transform->get_mat().xform_point(LVector3::rfu(mouse_point[0], 0, mouse_point[1]));
}

/**
 * Called when the user changes the frame size.  Assumes the lock is held.
 */
void PGItem::
frame_changed() {
  mark_frames_stale();
  if (_notify != nullptr) {
    _notify->item_frame_changed(this);
  }
}

/**
 * Returns the Node that is the root of the subgraph that will be drawn when
 * the PGItem is in the indicated state.  The first time this is called for a
 * particular state index, it may create the Node.
 *
 * Assumes the lock is already held.
 */
NodePath &PGItem::
do_get_state_def(int state) {
  slot_state_def(state);

  if (_state_defs[state]._root.is_empty()) {
    // Create a new node.
    _state_defs[state]._root = NodePath("state_" + format_string(state));
    _state_defs[state]._frame_stale = true;
  }

  if (_state_defs[state]._frame_stale) {
    update_frame(state);
  }

  return _state_defs[state]._root;
}

/**
 * Ensures there is a slot in the array for the given state definition.
 * Assumes the lock is already held.
 */
void PGItem::
slot_state_def(int state) {
  while (state >= (int)_state_defs.size()) {
    _state_defs.push_back(StateDef());
  }
}

/**
 * Generates a new instance of the frame geometry for the indicated state.
 * Assumes the lock is already held.
 */
void PGItem::
update_frame(int state) {
  // First, remove the old frame geometry, if any.
  if (state >= 0 && state < (int)_state_defs.size()) {
    _state_defs[state]._frame.remove_node();
  }

  // We must turn off the stale flag first, before we call get_state_def(), to
  // prevent get_state_def() from being a recursive call.
  _state_defs[state]._frame_stale = false;

  // Now create new frame geometry.
  if (has_frame()) {
    NodePath &root = do_get_state_def(state);
    _state_defs[state]._frame =
      _state_defs[state]._frame_style.generate_into(root, _frame);
  }
}

/**
 * Marks all the frames in all states stale, so that they will be regenerated
 * the next time each state is requested.  Assumes the lock is already held.
 */
void PGItem::
mark_frames_stale() {
#ifdef THREADED_PIPELINE
  // If we are using the threaded pipeline, we must update the frame geometry
  // immediately on the App thread, since this class isn't pipeline-cycled.
  if (Pipeline::get_render_pipeline()->get_num_stages() > 1) {
    for (int state = 0; state < (int)_state_defs.size(); ++state) {
      update_frame(state);
    }
  }
  else
#endif
  {
    for (StateDef &def : _state_defs) {
      // Remove the old frame, if any.
      def._frame.remove_node();
      def._frame_stale = true;
    }
  }
  mark_internal_bounds_stale();
}

/**
 * Clips the four corners of the item's frame by the indicated clipping plane,
 * and modifies the points to reflect the new set of clipped points.
 *
 * The return value is true if the set of points is unmodified (all points are
 * behind the clip plane), or false otherwise.
 */
bool PGItem::
clip_frame(ClipPoints &source_points, const LPlane &plane) const {
  if (source_points.empty()) {
    return true;
  }

  LPoint3 from3d;
  LVector3 delta3d;
  if (!plane.intersects_plane(from3d, delta3d, LPlane(LVector3(0, 1, 0), LPoint3::zero()))) {
    // The clipping plane is parallel to the polygon.  The polygon is either
    // all in or all out.
    if (plane.dist_to_plane(LPoint3::zero()) < 0.0) {
      // A point within the polygon is behind the clipping plane: the polygon
      // is all in.
      return true;
    }
    return false;
  }

  // Project the line of intersection into the X-Z plane.  Now we have a 2-d
  // clipping line.
  LPoint2 from2d(from3d[0], from3d[2]);
  LVector2 delta2d(delta3d[0], delta3d[2]);

  PN_stdfloat a = -delta2d[1];
  PN_stdfloat b = delta2d[0];
  PN_stdfloat c = from2d[0] * delta2d[1] - from2d[1] * delta2d[0];

  // Now walk through the points.  Any point on the left of our line gets
  // removed, and the line segment clipped at the point of intersection.

  // We might increase the number of vertices by as many as 1, if the plane
  // clips off exactly one corner.  (We might also decrease the number of
  // vertices, or keep them the same number.)
  ClipPoints new_points;
  new_points.reserve(source_points.size() + 1);

  LPoint2 last_point(source_points.back());
  bool last_is_in = is_right(last_point - from2d, delta2d);
  bool all_in = last_is_in;

  for (LPoint2 this_point : source_points) {
    bool this_is_in = is_right(this_point - from2d, delta2d);

    // There appears to be a compiler bug in gcc 4.0: we need to extract this
    // comparison outside of the if statement.
    bool crossed_over = (this_is_in != last_is_in);
    if (crossed_over) {
      // We have just crossed over the clipping line.  Find the point of
      // intersection.
      LVector2 d = this_point - last_point;
      PN_stdfloat denom = (a * d[0] + b * d[1]);
      if (denom != 0.0) {
        PN_stdfloat t = -(a * last_point[0] + b * last_point[1] + c) / denom;
        LPoint2 p = last_point + t * d;

        new_points.push_back(p);
        last_is_in = this_is_in;
      }
    }

    if (this_is_in) {
      // We are behind the clipping line.  Keep the point.
      new_points.push_back(this_point);
    } else {
      all_in = false;
    }

    last_point = this_point;
  }

  source_points.swap(new_points);
  return all_in;
}
